﻿using System;
using System.ComponentModel;
using System.Linq;
using StepsManagerLibrary.Enum;
using StepsManagerLibrary.Events;
using StepsManagerLibrary.Interfaces;

namespace StepsManagerLibrary
{
    /// <summary>
    /// Defines a set of Methods that enables a StepManager to work on items
    /// that implement the IStepControl interface
    /// </summary>
    /// <typeparam name="T">
    /// IStepControl : Control specifically designed to be managed as a Step
    /// </typeparam>
    public class StepControlManager<T> : BaseStepManager<T>,
        IStepControlManager<T>, IActionInitiator where T : class, IStepControl
    {
        #region Members of IStepManager<T>

        public override T CurrentStep
        {
            get
            {
                return base.CurrentStep;
            }
            protected set
            {
                if (_currentStep != value)
                {
                    // Informing the subscribers of the old step that it has moved out
                    if (_currentStep != null)
                    {
                        _currentStep.State = StepControlStateEnum.NotSelected;
                    }

                    base.CurrentStep = value;

                    // Informing the subscribers of the new step that it has been selected
                    if (_currentStep != null)
                    {
                        _currentStep.State = StepControlStateEnum.Selected;
                    }
                }
            }
        }

        #region Steps List

        /// <summary>
        /// Add a Step at the end of the current collection of steps
        /// </summary>
        /// <param name="step">Step to be added</param>
        /// <returns>The position of the added step</returns>
        public override int AddStep(T step)
        {
            // Check if the step can be added
            if (!CanAddStep(step))
                return -1;

            // Adding the step
            int result = base.AddStep(step);

            // If the step has been inserted we register the event
            if (result != -1)
            {
                step.PropertyChanged += StepOnPropertyChanged;
                step.ActionExpected += StepOnActionExpected;
            }

            return result;
        }

        /// <summary>
        /// Insert a step at a specific position in the list
        /// </summary>
        /// <param name="step">Step to be added</param>
        /// <param name="pos">Position to add the step</param>
        /// <returns>The step has been added correctly</returns>
        public override bool InsertStepAt(T step, int pos)
        {
            // Check if the step can be inserted
            if (!CanAddStep(step))
                return false;

            // Insert the step
            bool result = base.InsertStepAt(step, pos);

            // If the step has been inserted we register the event
            if (result)
            {
                step.PropertyChanged += StepOnPropertyChanged;
            }

            return result;
        }

        /// <summary>
        /// If the step provided exist in the collection
        /// removes the step from the collection
        /// </summary>
        /// <param name="step">Step to be removed</param>
        /// <returns>The step has been found and removed</returns>
        public override bool RemoveStep(T step)
        {
            if (_steps == null || !_steps.Contains(step))
                return false;

            _steps.Remove(step);
            return true;
        }

        /// <summary>
        /// If the step provided exist in the collection
        /// removes the step from the collection
        /// </summary>
        /// <param name="stepTitle">Step Title of the step to be removed</param>
        /// <returns>The step has been found and removed</returns>
        public virtual bool RemoveStep(string stepTitle)
        {
            T step = _steps.FirstOrDefault(s => s.Title == stepTitle);

            return RemoveStep(step);
        }

        #endregion

        #region Navigation

        /// <summary>
        /// Go to the first step of the collection
        /// </summary>
        /// <returns>Move has been successfully performed</returns>
        public override bool GoToFirst()
        {
            // To move from a step to another step we need the Manager to have
            // steps and not currently moving to another step
            if (CanDoAMoveOperation())
            {
                return MoveFromStepTo(_currentStep, _steps.First());
            }

            // If the move is not authorized, return null
            return false;
        }

        /// <summary>
        /// Go to the last step of the collection
        /// </summary>
        /// <returns>Move has been successfully performed</returns>
        public override bool GoToLast()
        {
            if (CanDoAMoveOperation())
            {
                return MoveFromStepTo(_currentStep, _steps.Last());
            }

            // If the move is not authorized, return null
            return true;
        }

        /// <summary>
        /// Go to the previous step of the collection
        /// </summary>
        /// <returns>Move has been successfully performed</returns>
        public override bool GoToPrevious()
        {
            if (CanDoAMoveOperation())
            {
                int pos = _steps.IndexOf(_currentStep);

                if (pos > 0)
                    --pos;

                return MoveFromStepTo(_currentStep, _steps[pos]);
            }

            return false;
        }

        /// <summary>
        /// Go to the next step of the collection
        /// </summary>
        /// <returns>Move has been successfully performed</returns>
        public override bool GoToNext()
        {
            if (CanDoAMoveOperation())
            {
                int pos = _steps.IndexOf(_currentStep);

                if (pos < _steps.Count - 1)
                    ++pos;

                return MoveFromStepTo(_currentStep, _steps[pos]);
            }

            return false;
        }

        /// <summary>
        /// Go to the step provided by the user
        /// </summary>
        /// <param name="step">Step of the collection</param>
        /// <returns>Move has been successfully performed</returns>
        public override bool GoTo(T step)
        {
            if (CanDoAMoveOperation() && _steps.Contains(step))
            {
                return MoveFromStepTo(_currentStep, step);
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stepTitle"></param>
        /// <returns></returns>
        public virtual bool GoTo(string stepTitle)
        {
            T step = _steps.FirstOrDefault(s => s.Title == stepTitle);

            return GoTo(step);
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Operation to move from a Step A to a Step B
        /// </summary>
        /// <param name="sourceStep">Step from which we want to move from</param>
        /// <param name="destinationStep">Step where we want to move at</param>
        /// <returns>Move has been successful</returns>
        protected bool MoveFromStepTo(T sourceStep, T destinationStep)
        {
            // Source and destination must be specified
            if (sourceStep == null || destinationStep == null)
                return false;

            // When we want to move to ourself
            if (sourceStep == destinationStep)
                return false;

            // Is the operation can be done
            if (!CanDoAMoveOperation() || !_steps.Contains(destinationStep))
                return false;

            // Do the steps authorize the transition
            if (!sourceStep.CanMoveOut() || !destinationStep.CanMoveIn())
                return false;

            // Notifying that a transition is occuring
            SourceStep = sourceStep;
            DestinationStep = destinationStep;

            // If any of the transition method fail the transition is cancelled
            bool result = _sourceStep.BeforeMovingOut();

            // Updating the state of the IStepControl
            // This will notify the observers that the state has changed
            _sourceStep.State = StepControlStateEnum.MovingFrom;
            _destinationStep.State = StepControlStateEnum.MovingTo;

            if (result)
                result &= _sourceStep.MoveOut();

            if (result)
                result &= _sourceStep.AfterMovingOut();

            if (result)
                result &= _destinationStep.BeforeMovingIn();

            if (result)
                result &= _destinationStep.MoveIn();

            if (result)
                result &= _destinationStep.AfterMovingIn();

            CurrentStep = !result ? SourceStep : DestinationStep;

            SourceStep = null;
            DestinationStep = null;

            return result;
        }

        // TODO : Add an event so that it is not using INotifyPropertyChanged
        // TODO : But an event informing us of the Action to do. For example move
        // TODO : to a specific Step, move to the next step, move previous...
        protected void StepOnPropertyChanged(object sender,
            PropertyChangedEventArgs propertyChangedEventArgs)
        {
            // We verify that the modification has been sent from
            // an IStepControl currently managed by the StepsManager
            if (sender is T && _steps.Contains(sender))
            {
                T stepControl = sender as T;

                if (propertyChangedEventArgs.PropertyName == "State")
                {
                    switch (stepControl.State)
                    {
                        //// If the state has changed to RequestedSelection
                        //// We have an event telling us that we want to move to 
                        //// a the stepControl that fired the event
                        //case StepControlStateEnum.RequestedSelection:
                        //    this.MoveFromStepTo(_currentStep, stepControl);
                        //    break;
                    }
                }
            }
            else
            {
                // Debugging purpose only
                throw new Exception("An error occured with the event logic");
            }
        }

        /// <summary>
        /// Determine if a step can be added to the StepsManager
        /// </summary>
        /// <param name="stepToBeAdded">Step looking for insert</param>
        /// <returns>Whether the step can be added</returns>
        private bool CanAddStep(IStepControl stepToBeAdded)
        {
            if (stepToBeAdded == null)
                return false;

            // The step should have a unique title
            return _steps.Count(step => step.Title == stepToBeAdded.Title) == 0;
        }

        #endregion

        #region IActionInitiator Members

        private void StepOnActionExpected(object sender,
            StepActionTypeEnum actionType,
            ActionEventArgs actionEventArgs)
        {
            StartAction(actionType, actionEventArgs);
        }

        public bool StartAction(StepActionTypeEnum actionType, ActionEventArgs args)
        {
            bool result = false;

            switch (actionType)
            {
                // Step List Actions
                case StepActionTypeEnum.AddStep:
                    if (args == null)
                        return false;

                    result = (AddStep(args.Step as T) != -1);

                    break;

                case StepActionTypeEnum.RemoveStep:
                    if (args == null)
                        return false;

                    result = args.Step == null ? RemoveStep(args.Title) : RemoveStep(args.Step as T);

                    break;

                // Navigation Action
                case StepActionTypeEnum.MoveFirst:
                    result = GoToFirst();
                    break;

                case StepActionTypeEnum.MoveLast:
                    result = GoToLast();
                    break;

                case StepActionTypeEnum.MoveNext:
                    result = GoToNext();
                    break;

                case StepActionTypeEnum.MovePrevious:
                    result = GoToPrevious();
                    break;

                case StepActionTypeEnum.MoveTo:
                    if (args == null)
                        return false;

                    result = args.Step == null ? GoTo(args.Title) : GoTo(args.Step as T);

                    break;
            }

            return result;
        }

        #endregion
    }
}